package xj.graph2d.app.prop;

import static xj.app.BaseApplicationFrame.getResourceString;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

import java.util.*;

import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.BevelBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import xj.graph2d.DrawShape;
import xj.graph2d.app.DrawApp;
import xj.meta.ModelElement;
import xj.model.uml.ActivityDiagram;
import xj.model.uml.ActivityFlow;
import xj.model.uml.ActivityNode;
import xj.model.uml.Actor;
import xj.model.uml.ClassDiagram;
import xj.model.uml.Enumeration;
import xj.model.uml.ExecutionOccurance;
import xj.model.uml.HyperState;
import xj.model.uml.LifeLine;
import xj.model.uml.Note;
import xj.model.uml.OrthogonalState;
import xj.model.uml.SequenceDiagram;
import xj.model.uml.State;
import xj.model.uml.StateDiagram;
import xj.model.uml.SubmachineState;
import xj.model.uml.Transition;
import xj.model.uml.UMLAssociation;
import xj.model.uml.UMLClass;
import xj.model.uml.UMLInterface;
import xj.model.uml.UMLMessage;
import xj.model.uml.UMLObject;
import xj.model.uml.UMLPackage;
import xj.model.uml.UMLSystem;
import xj.model.uml.UMLSystemBoundary;
import xj.model.uml.UseCase;
import xj.model.uml.UseCaseDiagram;

public class ModelPropertiesPane extends JPanel {

  public ModelPropertiesPane(DrawApp main) {
    this.main = main;
    //setBackground(Color.white);
    setLayout(new BorderLayout());
    title = new JLabel();
    //title.setFont(Style.editorFont);
    add(title, BorderLayout.NORTH);

    initPanels();

    panel = new JPanel();
    panel.setLayout(cards = new CardLayout());    
    for (String name : modelPanelMap.keySet()) {
      panel.add(name, modelPanelMap.get(name));
    }
    add(panel, BorderLayout.CENTER);
  }

  // the names are only used as keys of card layout
  protected static final String BLANK               = "Blank";
  protected static final String DIAGRAM             = "Diagram";
  protected static final String PACKAGE             = "Package";
  protected static final String CLASS               = "Class";
  protected static final String ENUM                = "Enum";
  protected static final String ASSOCIATION         = "Association";
  protected static final String GENERALIZATION      = "Generalization";
  protected static final String DELEGATION          = "Delegation";
  protected static final String DEPENDENCY          = "Dependency";
  protected static final String USE_CASE_DEPENDENCY = "UseCaseDependency";
  protected static final String USE_CASE            = "UseCase";
  protected static final String ACTOR               = "Actor";
  protected static final String SYSTEM_BOUNDARY     = "SystemBoundary";
  protected static final String SYSTEM              = "System";
  protected static final String OBJECT              = "Object";
  protected static final String MESSAGE             = "Message";
  protected static final String SIMPLE_STATE        = "SimpleState";
  protected static final String STATE_MACHINE       = "StateMachine";
  protected static final String SUBMACHINE          = "Submachine";
  protected static final String STATE               = "State";
  protected static final String TRANSITION          = "Transition";
  protected static final String ACTIVITY_NODE       = "ActivityNode";
  protected static final String ACTIVITY_FLOW       = "ActivityFlow";
  protected static final String INTERFACE           = "Interface";
  protected static final String NOTES               = "Notes";

  protected void initPanels() {
    JPanel blank = new JPanel();
    //blank.setBackground(Color.white);

    modelPanelMap.put(BLANK, blank);

    // diagram
    modelPanelMap.put(DIAGRAM, new UMLDiagramModelPane(main));

    // basic
    modelPanelMap.put(PACKAGE, new UMLPackageModelPane(main));

    modelPanelMap.put(CLASS, new UMLClassModelPane(main));

    modelPanelMap.put(ASSOCIATION, new UMLAssociationModelPane(main));

    modelPanelMap.put(GENERALIZATION, new UMLGeneralizationModelPane(main, generalizationTypes));

    modelPanelMap.put(DELEGATION, new UMLDelegationModelPane(main));

    modelPanelMap.put(DEPENDENCY, new UMLDependencyModelPane(main, dependencyTypes));

    modelPanelMap.put(USE_CASE_DEPENDENCY, 
		      new UMLDependencyModelPane(main, useCaseDependencyTypes));

    modelPanelMap.put(USE_CASE, new UMLUseCaseModelPane(main));

    modelPanelMap.put(ACTOR, new UMLActorModelPane(main));

    modelPanelMap.put(SYSTEM_BOUNDARY, new UMLSystemBoundaryModelPane(main));

    modelPanelMap.put(SYSTEM, new UMLSystemModelPane(main));

    // sequence diagram
    modelPanelMap.put(OBJECT, new UMLObjectModelPane(main));

    // "LifeLine",
    // "ExecutionOccurance",

    modelPanelMap.put(MESSAGE, new UMLMessageModelPane(main));

    // state diagram
    modelPanelMap.put(SIMPLE_STATE, 
		      new UMLStateModelPane(main, State.StateType.Simple_State));

    modelPanelMap.put(STATE_MACHINE, 
		      new UMLStateModelPane(main, State.StateType.State_Machine));

    modelPanelMap.put(SUBMACHINE, 
		      new UMLStateModelPane(main, State.StateType.Submachine_State));

    modelPanelMap.put(STATE, new UMLStateModelPane(main, null));

    modelPanelMap.put(TRANSITION, new UMLTransitionModelPane(main));

    // activity digram
    modelPanelMap.put(ACTIVITY_NODE, new UMLActivityNodeModelPane(main));

    modelPanelMap.put(ACTIVITY_FLOW, new UMLActivityFlowModelPane(main));

    modelPanelMap.put(INTERFACE, new UMLInterfaceModelPane(main));

    modelPanelMap.put(ENUM, new UMLEnumModelPane(main));

    modelPanelMap.put(NOTES, new UMLNotesModelPane(main));
  }

  public void setOwner(TabbedAttributePane owner) {
    this.owner = owner;
  }

  public void show(DrawShape shape) {
    if (debug) { 
      System.out.println("ModelPropertiesPane.show() @1 shape=" + shape);
    }
    curShape = shape;
    int pane = TabbedAttributePane.VISUAL_PANE;
    if (curShape != null) {
      boolean showModel = false;
      //ModelElement model = curShape.getModel();
      ModelElement model = shape.getModel();
      // display the model of the nearest ancestor 
      while (shape != null && 
	     !hasModelPanel(model)) { 
	DrawShape p = shape.getParent();     	
	if (p == null) { 
	  break; 
	} else { 
	  shape = p; 
	  model = p.getModel();
	}
      }

      if (debug) { 
	System.out.println("ModelPropertiesPane.show() (model != null): " + (model != null) + 
			   " model=" + model + 
			   " " + (model != null ? model.getClass().getName() : "") + 
			   " shape=" + shape);
      }

      if (model != null) {
	showModel = true;
	if (model instanceof UMLPackage) {
	  showPanel(PACKAGE, shape);
	} else if (model instanceof UMLClass) {	  
	  showPanel(CLASS, shape);
	} else if (model instanceof Enumeration) {	  
	  showPanel(ENUM, shape);
	} else if (model instanceof Note) {
	  showPanel(NOTES, shape);
	} else if (model instanceof UMLAssociation) {
	  UMLAssociation assoc = (UMLAssociation) model;
	  UMLAssociation.AssociationType atype = assoc.getType();
	  switch (atype) {
	  case Association:
	  case Aggregation:
	  case Composition:
	    showPanel(ASSOCIATION, shape);
	    break;
	  case Dependency:
	    showPanel(DEPENDENCY, shape);
	    break;
	  case UseCaseDependency:
	    showPanel(USE_CASE_DEPENDENCY, shape);
	    break;
	  case Generalization:
	  case Realization:
	  case MixIn:
	    showPanel(GENERALIZATION, shape);
	    break;
	  case Delegation:
	    showPanel(DELEGATION, shape);
	    break;

	  default:
	    showModel = false;
	  }
	} else if (model instanceof UseCase) {
	  showPanel(USE_CASE, shape);
	} else if (model instanceof Actor) {
	  showPanel(ACTOR, shape);
	} else if (model instanceof UMLSystemBoundary) {
	  showPanel(SYSTEM_BOUNDARY, shape);
	} else if (model instanceof UMLSystem) {
	  showPanel(SYSTEM, shape);
	} else if (model instanceof UMLObject) {
	  showPanel(OBJECT, shape);
	} else if (model instanceof UMLMessage) {
	  showPanel(MESSAGE, shape);
	} else if (model instanceof State) {
	  State state = (State) model;
	  switch (state.getType()) {
	  case Simple_State:
	  case Hyper_State:
	  case Orthogonal_State:
	    showPanel(SIMPLE_STATE, shape);
	    break;

	  case State_Machine:
	    showPanel(STATE_MACHINE, shape);
	    break;

	  case Submachine_State:
	    showPanel(SUBMACHINE, shape);
	    break;

	  case Entry_Point:
	  case Exit_Point:
	    showPanel(STATE, shape);
	    break;

	  default:
	    showModel = false;
	  }
	} else if (model instanceof Transition) {
	  showPanel(TRANSITION, shape);
	} else if (model instanceof ActivityNode) {
	  ActivityNode anode = (ActivityNode) model;
	  switch (anode.getType()) {
	  case Computation:
	  case Object:
	  case DataStore:
	  case Accept_Event:
	  case Send_Event:
	    showPanel(ACTIVITY_NODE, shape);
	    break;
	  case Decision:
	  default:
	    showModel = false;
	  }
	} else if (model instanceof ActivityFlow) {
	  showPanel(ACTIVITY_FLOW, shape);
	} else if (model instanceof UMLInterface) {
	  showPanel(INTERFACE, shape);

	} else if (model instanceof LifeLine) {
	  showModel = false;
	  // showPanel(9, shape);
	} else if (model instanceof ExecutionOccurance) {
	  showModel = false;
	  // showPanel(10, shape);
	} else if (model instanceof HyperState) {
	  showModel = false;
	} else if (model instanceof OrthogonalState) {
	  showModel = false;
	} else if (model instanceof SubmachineState) {
	  showModel = false;
	} else if (model instanceof xj.model.uml.Action) {
	  showModel = false;
	} else if (model instanceof ActivityDiagram) {
	  showModel = false;
	} else if (model instanceof ClassDiagram) {
	  showModel = false;
	} else if (model instanceof SequenceDiagram) {
	  showModel = false;
	} else if (model instanceof StateDiagram) {
	  showModel = false;
	} else if (model instanceof UseCaseDiagram) {
	  showModel = false;
	} else {
	  showModel = false;
	}
      }

      if (showModel) {
	pane = TabbedAttributePane.MODEL_PANE;
      } else {
	// show blank
	cards.show(panel, BLANK);
	title.setText("");
	/*
	if (titleString != null) {
	  pane = TabbedAttributePane.MODEL_PANE;
	}
	*/
      }
      repaint();
    } else { 
      // curShpae == null : no shape selected
      showPanel(DIAGRAM, shape);  
      pane = TabbedAttributePane.MODEL_PANE;
    }
    if (owner != null) {
      owner.show(pane);
      // owner.show(TabbedAttributePane.MODEL_PANE);
    }

    if (debug) { 
      System.out.println("ModelPropertiesPane.show() @2");
    }
  }

  protected boolean hasModelPanel(ModelElement model) { 
    if (model != null) { 
      if (model instanceof UMLPackage ||
	  model instanceof UMLClass ||
	  model instanceof Note ||
	  model instanceof UseCase ||
	  model instanceof Actor ||
	  model instanceof UMLSystemBoundary ||
	  model instanceof UMLSystem ||
	  model instanceof UMLObject ||
	  model instanceof UMLMessage ||
	  model instanceof Transition ||
	  model instanceof ActivityFlow ||
	  model instanceof UMLInterface) {
	return true; 
      } else if (model instanceof UMLAssociation) {
	UMLAssociation assoc = (UMLAssociation) model;
	UMLAssociation.AssociationType atype = assoc.getType();
	switch (atype) {
	case Association:
	case Aggregation:
	case Composition:
	case Dependency:
	case UseCaseDependency:
	case Generalization:
	case Realization:
	case MixIn:
	case Delegation:
	  return true;
	default:
	  return false;
	}
      } else if (model instanceof State) {
	State state = (State) model;
	switch (state.getType()) {
	case Simple_State:
	case Hyper_State:
	case Orthogonal_State:
	case State_Machine:
	case Submachine_State:
	case Entry_Point:
	case Exit_Point:
	  return true;
	default:
	  return false; 
	}
      } else if (model instanceof ActivityNode) {
	ActivityNode anode = (ActivityNode) model;
	switch (anode.getType()) {
	case Computation:
	case Object:
	case DataStore:
	case Accept_Event:
	case Send_Event:
	case Decision:
	  return true;
	default:
	  return false;
	}
      }
    }
    return false; 
  }

  public JPanel getCurrentPanel() { 
    return currentPanel; 
  }

  protected void showPanel(String name, DrawShape s) {
    if (name != null) {
      JPanel p = modelPanelMap.get(name); 
      if (p != null) {
	cards.show(panel, name);
	currentPanel = p; 
	if (p instanceof PropertiesPane) {
	  PropertiesPane prop = (PropertiesPane) p;
	  prop.setDrawShape(s, main);
	  title.setText(prop.getTitleString());
	}
      }
    }
  }

  protected Map<String, JPanel> modelPanelMap = new HashMap<String, JPanel>();

  protected static final Object[] dependencyTypes = { 
    new ChoiceValue("(default)", null), 
    "import", 
    "merge"
  };

  protected static final Object[] useCaseDependencyTypes = { 
    new ChoiceValue("(default)", null),
    "include", 
    "extend" 
  };

  protected static final Object[] generalizationTypes = { 
    UMLAssociation.AssociationType.Generalization,
    UMLAssociation.AssociationType.Realization, 
    UMLAssociation.AssociationType.MixIn, 
  };

  protected DrawApp main;

  protected TabbedAttributePane owner;

  protected DrawShape curShape;

  protected JLabel title;

  protected JPanel panel;

  protected CardLayout cards;

  protected JPanel currentPanel; 

  protected static final boolean debug = false;

  static abstract class TextComponentField extends ModelPropertyField {

    TextComponentField(String fieldName, String displayName) {
      super(fieldName, displayName);

      initTextComponent();
      if (text != null) {
	//text.setFont(Style.font);
	listener = new DocumentListener() {
	  public void insertUpdate(DocumentEvent e) {
	    // System.out.println("DocumentListener.insertUpdate() " + e);
	    textChanged();
	  }

	  public void removeUpdate(DocumentEvent e) {
	    // System.out.println("DocumentListener.removeUpdate() " + e);
	    textChanged();
	  }

	  public void changedUpdate(DocumentEvent e) {
	    // System.out.println("DocumentListener.changedUpdate() " + e);
	    textChanged();
	  }

	  private void textChanged() {
	    //System.out.println("ModelPropertiesPane.TextComponentField.textChanged()");
	    setModelValue(toModelValue(text.getText()));
	    updateShape();
	  }
	};
	text.getDocument().addDocumentListener(listener);
	setEditor(text);
      }
    }

    @Override
    public void updateEditorValue() {
      text.getDocument().removeDocumentListener(listener);
      Object v = getModelValue();
      if (v == null) {
	text.setText(null);
      } else if (v instanceof String) {
	text.setText((String) v);
      } else { 
	text.setText(v.toString());
      }
      text.getDocument().addDocumentListener(listener);
    }

    @Override
    public void updateModelValue() {
      String s = text.getText();
      setModelValue(toModelValue(s));
    }

    protected Object toModelValue(String text) { 
      return text; 
    }

    abstract protected void initTextComponent();

    protected javax.swing.text.JTextComponent text;

    protected DocumentListener listener;

  }

  static class NameField extends TextComponentField {
    NameField(String fieldName, String displayName) {
      super(fieldName, displayName);
    }

    @Override
    protected void initTextComponent() {
      text = new JTextField(10);
    }

  }

  static class CheckBoxField extends ModelPropertyField {

    CheckBoxField(String fieldName, String displayName) {
      super(fieldName, displayName);
      cbox = new JCheckBox();

      cbox.addItemListener(new ItemListener() {
	public void itemStateChanged(ItemEvent e) {
	  //System.out.println("ModelPropertiesPane.CheckBoxField.itemStateChanged()");

	  setModelValue(new Boolean(cbox.isSelected()));
	  if (excludes.size() > 0 && 
	      cbox.isSelected()) {
	    for (CheckBoxField cb : excludes) { 
	      cb.cbox.setSelected(false);
	      cb.setModelValue(false);
	    }
	  }
	  updateShape();
	}
      });

      setEditor(cbox);
    }

    public void addExclude(CheckBoxField exclude) {
      if (exclude != null && 
	  !excludes.contains(exclude)) { 
	excludes.add(exclude);
	exclude.addExclude(this);
      }
    }

    @Override
    public void updateEditorValue() {
      Object v = getModelValue();
      if (v instanceof Boolean) {
	cbox.setSelected(((Boolean) v).booleanValue());
      }
    }

    @Override
    public void updateModelValue() {
      boolean b = cbox.isSelected();
      setModelValue(new Boolean(b));
    }

    protected JCheckBox cbox;
    protected List<CheckBoxField> excludes = new ArrayList<CheckBoxField>();
  }

  static class TextField extends TextComponentField {

    TextField(String fieldName) {
      this(fieldName, null);
    }

    TextField(String fieldName, String displayName) {
      super(fieldName, displayName);
    }

    @Override
    protected void initTextComponent() {
      text = new JTextArea(10, 30);
      text.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
    }

  }

  static class ChoiceValue { 
    ChoiceValue(String displayName, Object value) {
      this.displayName = displayName;
      this.value = value;
    }

    @Override
    public String toString() { 
      return displayName;
    }
    
    String displayName;
    Object value; 
  }

  static class ChoiceField extends ModelPropertyField {

    ChoiceField(String fieldName, String displayName, Object[] values) {
      super(fieldName, displayName);
      cbox = new JComboBox();
      //cbox.setFont(Style.font);
      //cbox.setBackground(Color.white);
      if (values != null) {
	int n = values.length;
	for (int i = 0; i < n; i++) {
	  cbox.addItem(values[i]);
	}
      }
      cbox.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent e) {
	  //System.out.println("ModelPropertiesPane.ChoiceField.actionPerformed()");

	  if (cbox.getSelectedIndex() < 0) {
	    setModelValue(null);
	  } else {
	    Object v = cbox.getSelectedItem();
	    if (v instanceof ChoiceValue) { 
	      ChoiceValue cv = (ChoiceValue) v;
	      setModelValue(cv.value);
	    } else { 
	      setModelValue(v);
	    }
	  }
	  updateShape();
	}
      });
      setEditor(cbox);
    }

    @Override
    public void updateEditorValue() {
      Object v = getModelValue();
      if (v != null) {
	cbox.setSelectedItem(v);
      } else {
	cbox.setSelectedIndex(0);
      }
    }

    @Override
    public void updateModelValue() {
      if (cbox.getSelectedIndex() < 0) {
	setModelValue(null);
      } else {
	Object v = cbox.getSelectedItem();
	setModelValue(v);
      }
    }

    protected JComboBox cbox;

  }

}
